package cn.cl.service;

import cn.cl.bean.Employee;
import cn.cl.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;

/**
 * 标题：
 * 作者：何处是归程
 * 时间：2020/3/31 - 20:31
 */
@Service
public class EmployeeService {
	@Autowired
	EmployeeMapper employeeMapper;

	/**
	 * @Cacheable: 将方法的运行结果进行缓存，，以后再要相同的数据就直接从缓存中获取，不用调用方法
	 * CacheManager管理多个Cache组件的，对缓存的真正CRUD操作在Cache组件中，每一个缓存组件有自己唯一一个名字
	 * <p>
	 * 几个属性：
	 * cacheNames / value：指定缓存的名字
	 * key：缓存数据使用的Key，可以用它来指定，默认是使用方法参数的值
	 * 编写SpEL：#id 参数的ID值 #a0 #p0 # root.args[0]
	 * keyGenerator：key的生成器，可以自己指定key的生成器的组件id
	 * key / keyGenerator 二选一使用
	 * cacheManager：指定缓存管理器
	 * cacheResolver：缓存解析器 与 cacheManager一样
	 * condition：指定符合条件的情况下才缓存
	 * unless：当unless指定的条件为true，方法的返回值就不会被缓存，可以获取到结果进行判断  unless = "#result == null"
	 * sync：缓存是否使用异步方式
	 * <p>
	 * 原理：
	 * 自动配置类：CacheAutoConfiguration
	 * 缓存的配置类：
	 * org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
	 * org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
	 * org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
	 * org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
	 * org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
	 * org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
	 * org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
	 * org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
	 * org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
	 * org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
	 * 哪个配置类默认生效：SimpleCacheConfiguration
	 * 给容器中注册了一个ConcurrentMapCacheManager，可以获取和创建ConcurrentMapCache类的缓存组件，他的作用将数据保存在ConcurrentMap中
	 * 步骤@Cacheable：
	 * 1、方法运行之前，先去查询Cache(缓存组件)，按照cacheNames指定的名字获取（CacheManager先获取响应的缓存）第一次获取缓冲如果没有Cache组件会自动创建
	 * 2、去Cache中查找缓存的内容，使用一个key，默认就是方法的参数，key就是某种策略生成的，默认是使用keyGenerator生成的，默认使用SimpleKeuGenerator生成Key
	 * SimpleKeuGenerator生成Key的默认策略：
	 * 如果没有参数：key = new SimpleKey()
	 * 如果有一个参数：key = 参数的值
	 * 如果有多个参数：key = new SimpleKey(params)
	 * 3、没有查到缓存就调用目标方法
	 * 4、将目标方法返回的结果放进缓存中
	 * 一句话：@Cacheable标注的方法执行之前先检查缓存中有没有这个数据，默认按照参数的值作为key去查询缓存，如果没有进运行方法，并将结果放入缓存, 以后再调用就可以直接使用缓存中的数据
	 * 核心：
	 * 1、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
	 * 2、key使用keyGenerator生成的，默认是SimpleKeuGenerators
	 */
	@Cacheable(cacheNames = "emp", key = "#id", condition = "#id > 0")
	public Employee getEmpById(Integer id) {
		System.out.println("查询" + id + "号员工！");
		return employeeMapper.getEmpById(id);
	}

	/**
	 * @CachePut: 既调用方法又更新缓存数据， 同步更新缓存，修改了数据库的某个数据，同时更新缓存
	 * 运行实际：
	 * 先调用目标方法
	 * 将目标方法的结果缓存起来
	 * 测试步骤：
	 * 1、查询1号员工，查到的结果会放在缓存中
	 * key = id, value = 返回的employee对象
	 * 2、更新1号员工，【lastName = 皮卡丘】
	 * 将方法的返回值也放入缓存中，key = 传入的employee对象，value：返回的employee对象
	 * 3、查询1号员工，数据还是之前的
	 * 应该是更新之后的员工，为什么没有更新？ 【1号员工没有在缓存中更新】
	 * 解决办法：
	 * key = "#employee.id"
	 * key = "#result.id"
	 */
	@CachePut(value = "emp", key = "#employee.id")
	public Employee updateEmp(Employee employee) {
		System.out.println("update" + employee);
		employeeMapper.updateEmp(employee);
		return employee;
	}

	/**
	 * @CacheEvict: 缓存清除
	 * key = "#id"：指定要清除的数据
	 * allEntries = true：删除这个缓存中的所有的数据
	 * beforeInvocation = false：缓存的清除是否在方法之前执行，默认为在方法之后执行，如果方法发生异常就不会删除缓存
	 */
	@CacheEvict(value = "emp", key = "#id")
	public void deleteEmp(Integer id) {
		System.out.println("删除了id为：" + id + "的员工");
		employeeMapper.deleteEmp(id);
	}

	@Caching(
			cacheable = {
					@Cacheable(value = "emp", key = "#lastName")
			},
			put = {
					@CachePut(value = "emp", key = "#result.id"),
					@CachePut(value = "emp", key = "#result.email")
			}
	)
	public Employee getEmpByLastName(String lastName) {
		return employeeMapper.getEmpByLastName(lastName);
	}
}
